package com.xiaopo.flying.puzzle.slant;

import com.xiaopo.flying.puzzle.Area;
import com.xiaopo.flying.puzzle.Line;
import com.xiaopo.flying.puzzle.ext.PointF;
import com.xiaopo.flying.puzzle.ext.RectF;
import ohos.agp.render.Path;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import static com.xiaopo.flying.puzzle.slant.SlantUtils.distance;
import static com.xiaopo.flying.puzzle.slant.SlantUtils.getPoint;
import static com.xiaopo.flying.puzzle.slant.SlantUtils.intersectionOfLines;

/**
 * @author wupanjie
 */
class SlantArea implements Area {
    SlantLine lineLeft;
    SlantLine lineTop;
    SlantLine lineRight;
    SlantLine lineBottom;

    CrossoverPointF leftTop;
    CrossoverPointF leftBottom;
    CrossoverPointF rightTop;
    CrossoverPointF rightBottom;

    private PointF tempPointF;

    private float paddingLeft;
    private float paddingTop;
    private float paddingRight;
    private float paddingBottom;
    private float radian;

    private Path areaPath = new Path();
    private RectF areaRect = new RectF();
    private PointF[] handleBarPoints = new PointF[2];

    SlantArea() {
        handleBarPoints[0] = new PointF();
        handleBarPoints[1] = new PointF();

        leftTop = new CrossoverPointF();
        leftBottom = new CrossoverPointF();
        rightTop = new CrossoverPointF();
        rightBottom = new CrossoverPointF();

        tempPointF = new PointF();
    }

    SlantArea(SlantArea src) {
        this();
        this.lineLeft = src.lineLeft;
        this.lineTop = src.lineTop;
        this.lineRight = src.lineRight;
        this.lineBottom = src.lineBottom;

        this.leftTop = src.leftTop;
        this.leftBottom = src.leftBottom;
        this.rightTop = src.rightTop;
        this.rightBottom = src.rightBottom;

        updateCornerPoints();
    }

    @Override
    public float left() {
        return Math.min(leftTop.getPointX(), leftBottom.getPointX()) + paddingLeft;
    }

    @Override
    public float top() {
        return Math.min(leftTop.getPointY(), rightTop.getPointY()) + paddingTop;
    }

    @Override
    public float right() {
        return Math.max(rightTop.getPointX(), rightBottom.getPointX()) - paddingRight;
    }

    @Override
    public float bottom() {
        return Math.max(leftBottom.getPointY(), rightBottom.getPointY()) - paddingBottom;
    }

    @Override
    public float centerX() {
        return (left() + right()) / 2;
    }

    @Override
    public float centerY() {
        return (top() + bottom()) / 2;
    }

    @Override
    public float width() {
        return right() - left();
    }

    @Override
    public float height() {
        return bottom() - top();
    }

    @Override
    public PointF getCenterPoint() {
        return new PointF(centerX(), centerY());
    }

    public Path getAreaPath() {
        areaPath.reset();

        if (radian > 0) {
            float tempRatio = radian / distance(leftTop, leftBottom);
            getPoint(tempPointF, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio);
            tempPointF.offset(paddingLeft, paddingTop);
            areaPath.moveTo(tempPointF.getPointX(), tempPointF.getPointY());

            tempRatio = radian / distance(leftTop, rightTop);
            getPoint(tempPointF, leftTop, rightTop, Line.Direction.HORIZONTAL, tempRatio);
            tempPointF.offset(paddingLeft, paddingTop);
            areaPath.quadTo(leftTop.getPointX() + paddingLeft, leftTop.getPointY() + paddingTop, tempPointF.getPointX(), tempPointF.getPointY());

            tempRatio = 1 - tempRatio;
            getPoint(tempPointF, leftTop, rightTop, Line.Direction.HORIZONTAL, tempRatio);
            tempPointF.offset(-paddingRight, paddingTop);
            areaPath.lineTo(tempPointF.getPointX(), tempPointF.getPointY());

            tempRatio = radian / distance(rightTop, rightBottom);
            getPoint(tempPointF, rightTop, rightBottom, Line.Direction.VERTICAL, tempRatio);
            tempPointF.offset(-paddingRight, paddingTop);
            areaPath.quadTo(rightTop.getPointX() - paddingLeft, rightTop.getPointY() + paddingTop, tempPointF.getPointX(), tempPointF.getPointY());

            tempRatio = 1 - tempRatio;
            getPoint(tempPointF, rightTop, rightBottom, Line.Direction.VERTICAL, tempRatio);
            tempPointF.offset(-paddingRight, -paddingBottom);
            areaPath.lineTo(tempPointF.getPointX(), tempPointF.getPointY());

            tempRatio = 1 - radian / distance(leftBottom, rightBottom);
            getPoint(tempPointF, leftBottom, rightBottom, Line.Direction.HORIZONTAL, tempRatio);
            tempPointF.offset(-paddingRight, -paddingBottom);
            areaPath.quadTo(rightBottom.getPointX() - paddingRight, rightBottom.getPointY() - paddingTop, tempPointF.getPointX(), tempPointF.getPointY());

            tempRatio = 1 - tempRatio;
            getPoint(tempPointF, leftBottom, rightBottom, Line.Direction.HORIZONTAL, tempRatio);
            tempPointF.offset(paddingLeft, -paddingBottom);
            areaPath.lineTo(tempPointF.getPointX(), tempPointF.getPointY());

            tempRatio = 1 - radian / distance(leftTop, leftBottom);
            getPoint(tempPointF, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio);
            tempPointF.offset(paddingLeft, -paddingBottom);
            areaPath.quadTo(leftBottom.getPointX() + paddingLeft, leftBottom.getPointY() - paddingBottom, tempPointF.getPointX(), tempPointF.getPointY());

            tempRatio = 1 - tempRatio;
            getPoint(tempPointF, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio);
            tempPointF.offset(paddingLeft, paddingTop);
            areaPath.lineTo(tempPointF.getPointX(), tempPointF.getPointY());
        } else {
            areaPath.moveTo(leftTop.getPointX() + paddingLeft, leftTop.getPointY() + paddingTop);
            areaPath.lineTo(rightTop.getPointX() - paddingRight, rightTop.getPointY() + paddingTop);
            areaPath.lineTo(rightBottom.getPointX() - paddingRight, rightBottom.getPointY() - paddingBottom);
            areaPath.lineTo(leftBottom.getPointX() + paddingLeft, leftBottom.getPointY() - paddingBottom);
            areaPath.lineTo(leftTop.getPointX() + paddingLeft, leftTop.getPointY() + paddingTop);
        }
        return areaPath;
    }

    @Override
    public RectF getAreaRect() {
        areaRect.set(left(), top(), right(), bottom());
        return areaRect;
    }

    public boolean contains(float x, float y) {
        return SlantUtils.contains(this, x, y);
    }

    @Override
    public boolean contains(Line line) {
        return lineLeft == line || lineTop == line || lineRight == line || lineBottom == line;
    }

    @Override
    public boolean contains(PointF point) {
        return contains(point.getPointX(), point.getPointY());
    }

    @Override
    public List<Line> getLines() {
        return Arrays.asList((Line) lineLeft, lineTop, lineRight, lineBottom);
    }

    @Override
    public PointF[] getHandleBarPoints(Line line) {
        if (line == lineLeft) {
            getPoint(handleBarPoints[0], leftTop, leftBottom, line.direction(), 0.25f);
            getPoint(handleBarPoints[1], leftTop, leftBottom, line.direction(), 0.75f);
            handleBarPoints[0].offset(paddingLeft, 0);
            handleBarPoints[1].offset(paddingLeft, 0);
        } else if (line == lineTop) {
            getPoint(handleBarPoints[0], leftTop, rightTop, line.direction(), 0.25f);
            getPoint(handleBarPoints[1], leftTop, rightTop, line.direction(), 0.75f);
            handleBarPoints[0].offset(0, paddingTop);
            handleBarPoints[1].offset(0, paddingTop);
        } else if (line == lineRight) {
            getPoint(handleBarPoints[0], rightTop, rightBottom, line.direction(), 0.25f);
            getPoint(handleBarPoints[1], rightTop, rightBottom, line.direction(), 0.75f);
            handleBarPoints[0].offset(-paddingRight, 0);
            handleBarPoints[1].offset(-paddingRight, 0);
        } else if (line == lineBottom) {
            getPoint(handleBarPoints[0], leftBottom, rightBottom, line.direction(), 0.25f);
            getPoint(handleBarPoints[1], leftBottom, rightBottom, line.direction(), 0.75f);
            handleBarPoints[0].offset(0, -paddingBottom);
            handleBarPoints[1].offset(0, -paddingBottom);
        }
        return handleBarPoints;
    }

    @Override
    public float radian() {
        return radian;
    }

    @Override
    public void setRadian(float radian) {
        this.radian = radian;
    }

    @Override
    public float getPaddingLeft() {
        return paddingLeft;
    }

    @Override
    public float getPaddingTop() {
        return paddingTop;
    }

    @Override
    public float getPaddingRight() {
        return paddingRight;
    }

    @Override
    public float getPaddingBottom() {
        return paddingBottom;
    }

    @Override
    public void setPadding(float padding) {
        setPadding(padding, padding, padding, padding);
    }

    @Override
    public void setPadding(float paddingLeft, float paddingTop, float paddingRight, float paddingBottom) {
        this.paddingLeft = paddingLeft;
        this.paddingTop = paddingTop;
        this.paddingRight = paddingRight;
        this.paddingBottom = paddingBottom;
    }

    void updateCornerPoints() {
        intersectionOfLines(leftTop, lineLeft, lineTop);
        intersectionOfLines(leftBottom, lineLeft, lineBottom);
        intersectionOfLines(rightTop, lineRight, lineTop);
        intersectionOfLines(rightBottom, lineRight, lineBottom);
    }

    static class AreaComparator implements Comparator<SlantArea>, Serializable {

        @Override
        public int compare(SlantArea one, SlantArea two) {
            if (one.leftTop.getPointY() < two.leftTop.getPointY()) {
                return -1;
            } else if (one.leftTop.getPointY() == two.leftTop.getPointY()) {
                if (one.leftTop.getPointX() < two.leftTop.getPointX()) {
                    return -1;
                } else if (one.leftTop.getPointX() == two.leftTop.getPointX()) {
                    return 0;
                } else {
                    return 1;
                }
            } else {
                return 1;
            }
        }
    }
}
